from flask import request, jsonify, sessions
import hashlib
import json, datetime
from sqlalchemy.ext.declarative import DeclarativeMeta
import jwt
import time
from AdminApiRunner import config
from AdminApiRunner.app import db
from AdminApiRunner.app.models import User, Admin

class AlchemyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj.__class__, DeclarativeMeta):
            # an SQLAlchemy class
            fields = {}
            for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
                data = obj.__getattribute__(field)
                try:
                    json.dumps(data)  # this will fail on non-encodable values, like other classes
                    fields[field] = data
                except TypeError:  # 添加了对datetime的处理
                    if isinstance(data, datetime.datetime):
                        fields[field] = data.isoformat()
                    elif isinstance(data, datetime.date):
                        fields[field] = data.isoformat()
                    elif isinstance(data, datetime.timedelta):
                        fields[field] = (datetime.datetime.min + data).time().isoformat()
                    else:
                        fields[field] = None
            # a json-encodable dict
            return fields

        return json.JSONEncoder.default(self, obj)


def new_alchemy_encoder():
    _visited_objs = []

    class AlchemyEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj.__class__, DeclarativeMeta):
                # don't re-visit self
                if obj in _visited_objs:
                    return None
                _visited_objs.append(obj)

                # an SQLAlchemy class
                fields = {}
                for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
                    data = obj.__getattribute__(field)
                    try:
                        if isinstance(data, datetime):
                            data = data.strftime('%Y-%m-%d %H:%M:%S')
                        json.dumps(data)  # this will fail on non-encodable values, like other classes
                        fields[field] = data
                    except TypeError:
                        fields[field] = None
                return fields

            return json.JSONEncoder.default(self, obj)


class PasswdUtil(object):

    @staticmethod
    def md5(string):
        m = hashlib.md5()
        # 注意update()输入应为bytes类型，需用.encode('utf-8')将str转为bytes型
        m.update(string.encode('utf-8'))
        return m.hexdigest()

class ReturnUtil(object):

    @staticmethod
    def Success(status=1,msg="success",data=None):
        # return jsonify({
        #     "status":status,
        #     "msg"   : msg,
        #     "data"  : data
        # })
        return json.dumps({
            "status": status,
            "msg": msg,
            "data": data
        }, cls=AlchemyEncoder, check_circular=False)

    @staticmethod
    def Error(status=0, msg="error", data=None):
        # return jsonify({
        #     "status": status,
        #     "msg": msg,
        #     "data": data
        # })
        return json.dumps({
            "status": status,
            "msg": msg,
            "data": data
        }, cls=AlchemyEncoder, check_circular=False)


def trueReturn(data, msg):
    return {
        "status": True,
        "data": data,
        "msg": msg
    }

def falseReturn(data, msg):
    return {
        "status": False,
        "data": data,
        "msg": msg
    }


class AuthUtil(object):

    @staticmethod
    def encode_auth_token(user_id, login_time):
        """
        生成认证Token
        :param user_id: int
        :param login_time: int(timestamp)
        :return: string
        """
        try:
            payload = {
                'exp': datetime.datetime.utcnow() + datetime.timedelta(days=0, seconds=10),
                'iat': datetime.datetime.utcnow(),
                'iss': 'ken',
                'data': {
                    'id': user_id,
                    'login_time': login_time
                }
            }
            return jwt.encode(
                payload,
                config.SECRET_KEY,
                algorithm='HS256'
            )
        except Exception as e:
            return e

    @staticmethod
    def decode_auth_token(auth_token):
        """
        验证Token
        :param auth_token:
        :return: integer|string
        """
        try:
            # payload = jwt.decode(auth_token, app.config.get('SECRET_KEY'), leeway=datetime.timedelta(seconds=10))
            # 取消过期时间验证
            payload = jwt.decode(auth_token, config.SECRET_KEY, options={'verify_exp': False})
            if ('data' in payload and 'id' in payload['data']):
                return payload
            else:
                raise jwt.InvalidTokenError
        except jwt.ExpiredSignatureError:
            return 'Token过期'
        except jwt.InvalidTokenError:
            return '无效Token'

    @staticmethod
    def authenticate(username, password):
        """
        用户登录，登录成功返回token，写将登录时间写入数据库；登录失败返回失败原因
        :param password:
        :return: json
        """
        userInfo = User.query.filter_by(username=username).first()
        if (userInfo is None):
            return ReturnUtil.Error("未找到用户")
        else:
            if (User.check_password(User, userInfo.password, password)):
                login_time = int(time.time())
                userInfo.login_time = login_time
                db.session.add(userInfo)
                token = AuthUtil.encode_auth_token(userInfo.id, login_time)
                db.session.commit()
                db.session.close()
                return ReturnUtil.Success(msg="登录成功", data=token.decode())
            else:
                return ReturnUtil.Error("密码不正确")

    @staticmethod
    def identify(request):
        """
        用户鉴权
        :return: list
        """
        auth_header = request.headers.get('Authorization')
        if (auth_header):
            auth_tokenArr = auth_header.split(" ")
            if (not auth_tokenArr or auth_tokenArr[0] != config.SECRET_HEAD or len(auth_tokenArr) != 2):
                result = falseReturn('', '请传递正确的验证头信息')
            else:
                auth_token = auth_tokenArr[1]
                payload = AuthUtil.decode_auth_token(auth_token)
                if not isinstance(payload, str):
                    user = User.query.filter_by(id=payload['data']['id']).first()
                    if (user is None):
                        result = falseReturn('', '找不到该用户信息')
                    else:
                        if (user.login_time == payload['data']['login_time']):
                            result = trueReturn(user.id, '请求成功')
                        else:
                            result = falseReturn('', 'Token已更改，请重新登录获取')
                else:
                    result = falseReturn('', payload)
        else:
            result = falseReturn('', '没有提供认证token')
        return result